#! /usr/bin/env python

##
##    This program is free software; you can redistribute it and/or modify
##    it under the terms of the GNU General Public License as published by
##    the Free Software Foundation; either version 2 of the License, or
##     ( at your option )  any later version.
##
##    This program is distributed in the hope that it will be useful,
##    but WITHOUT ANY WARRANTY; without even the implied warranty of
##    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##    GNU General Public License for more details.
##
##    You should have received a copy of the GNU General Public License
##    along with this program; if not, write to the Free Software
##    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
##

import pygtk
pygtk.require('2.0')
import gtk, gobject, cairo, math, rsvg
from gtk import gdk
import gobject 

class CairoEventBox(gtk.EventBox):
    __gsignals__ = { 
            "expose-event": "override" ,
            "screen-changed": "override" }
            
    def __init__(self):
        super(CairoEventBox,self).__init__()
        self.supports_alpha = False

    def do_expose_event(self, event):
        cr = self.window.cairo_create()
        cr.set_operator(cairo.OPERATOR_CLEAR)
        # Ugly but we don't have event.region
        region = gtk.gdk.region_rectangle(event.area)
        cr.region(region)
        cr.fill()
        cr.set_operator(cairo.OPERATOR_OVER)
        if self.get_parent().bgsvg:
            cr.save()
            self.get_parent().bgsvg.render_cairo(cr)
            cr.restore()
            
        for c in self.get_parent().child_widgets:
            c.draw(cr,event)
        return False    

    def do_screen_changed(self,screen, old_screen=None):
        if screen:
            #screen = widget.get_screen()
            colormap = screen.get_rgba_colormap()
            if colormap == None:
                #print 'Your screen does not support alpha channels!'
                colormap = screen.get_rgb_colormap()
                self.supports_alpha = False
            else:
                #print 'Your screen supports alpha channels!'
                self.supports_alpha = True
                
            self.set_colormap(colormap)
        
        return True
    
            
class CairoWindow(gtk.Window):
    
    __gsignals__ = { 
                "expose-event": "override" ,
                    "screen-changed": "override" }
    
    def __init__(self):
        super(CairoWindow,self).__init__()
        self.connect("delete-event", gtk.main_quit)
        self.connect('destroy', self.destroy)
        
        self.supports_alpha = False
        self.child_widgets = []
        self.do_screen_changed(self)
        self.bgsvg = None
        self.eventbox = CairoEventBox()
        self.add(self.eventbox)

        self.show_all()
        self.cairo = self.window.cairo_create()
        #print self.get_display().supports_composite()
        #self.eventbox.window.set_composited(True)
        #self.connect_after("expose-event",self.window_expose_event)
        
    def add_child(self,w):
        w.set_parent(self)
        self.child_widgets.append(w)
        w.connect("request-draw",self.redraw_child)
    
    def remove_child(self,idx):
        self.child_widgets[idx].destroy()
        del self.child_widgets[idx]
    
    def redraw_child(self,w,e):
        #print "Requested redraw:",w
        self.eventbox.queue_draw()
        '''_area(w.allocation['x'],
                        w.allocation['y'],
                        w.allocation['w'],
                        w.allocation['h'])
        '''
    '''
    def window_expose_event(self,widget, event):
        #get our child (in this case, the event box)
        child = widget.get_child()
        
        #create a cairo context to draw to the window
        cr = widget.window.cairo_create()
        
        cr.set_operator(cairo.OPERATOR_CLEAR)
        # Ugly but we don't have event.region
        region = gtk.gdk.region_rectangle(event.area)
        cr.region(region)
        cr.fill()
        
        #cr.set_operator(cairo.OPERATOR_OVER)
        
        #the source data is the (composited) event box
        pix = gtk.gdk.Pixmap(child.window,child.window.get_size()[0],
        child.window.get_size()[1])
        
        cr.set_source_pixmap (pix,
                              child.allocation.x,
                              child.allocation.y)

        #draw no more than our expose event intersects our child
        region = gtk.gdk.region_rectangle(child.allocation)
        r = gtk.gdk.region_rectangle(event.area)
        region.intersect(r)
        cr.region (region)
        cr.clip()

        #composite, with a 50% opacity
        cr.set_operator(cairo.OPERATOR_OVER)
        cr.paint_with_alpha(0.5)

        return False
    '''
    def do_expose_event(self, event):

        cr = self.cairo
        cr.set_operator(cairo.OPERATOR_CLEAR)
        # Ugly but we don't have event.region
        region = gtk.gdk.region_rectangle(event.area)
        cr.region(region)
        cr.fill()
        #
        if self.supports_alpha:
            cr.set_source_rgba(1.0, 1.0, 1.0, 0.0)
        else:
            cr.set_source_rgb(1.0, 1.0, 1.0)
            
        cr.set_operator(cairo.OPERATOR_SOURCE)
        cr.paint()

        cr.set_operator(cairo.OPERATOR_OVER)

        if self.bgsvg:
            self.bgsvg.render_cairo(cr)
        
        #cr.region(region)
        #cr.clip()
        
        children = self.get_children()
        for c in children:
            self.propagate_expose(c, event)
            
        #for c in self.child_widgets:
        #    c.draw(cr,event)
            
        return False
        
    def set_background_svg(self, fn):
        self.bgsvg = rsvg.Handle(file=fn)
        self.queue_draw()
        
    def destroy(self,e):
        if self.bgsvg:
            self.bgsvg.close()
            
    def do_screen_changed(self,widget, old_screen=None):
        screen = widget.get_screen()
        colormap = screen.get_rgba_colormap()
        if colormap == None:
            #print 'Your screen does not support alpha channels!'
            colormap = screen.get_rgb_colormap()
            self.supports_alpha = False
        else:
            #print 'Your screen supports alpha channels!'
            self.supports_alpha = True
            
        widget.set_colormap(colormap)
        
        return True
    

class BaseWidget(gobject.GObject):
    
    __gsignals__ = {
        "clicked": (gobject.SIGNAL_RUN_FIRST, None, (gtk.gdk.Event,)), #Hmmmm, eh, for button
        "button-press-event": (gobject.SIGNAL_RUN_FIRST, None, (gtk.gdk.Event,)),
        "button-release-event": (gobject.SIGNAL_RUN_FIRST, None, (gtk.gdk.Event,)),
        "key-press-event": (gobject.SIGNAL_RUN_FIRST, None, (gtk.gdk.Event,)),
        "key-release-event": (gobject.SIGNAL_RUN_FIRST, None, (gtk.gdk.Event,)),
        "motion-notify-event": (gobject.SIGNAL_RUN_FIRST, None, (gtk.gdk.Event,)),
        "request-draw": (gobject.SIGNAL_RUN_FIRST, None,(object,))
    }
    
    def __init__(self):
        gobject.GObject.__init__(self)
        self.child_widgets = []
        self.size_request = None
        self.hit_path = None
        self.visible = True
        self.parent = None
        self.hit = False
        self.allocation = { 'x':0, 'y':0, 'w':128, 'h':128 } #Absolute position on parent window/eventbox
    
    def add_child(self,w):
        self.child_widgets.append(w)
        
    def set_parent(self, parent):
        self.parent = parent
        self._parent_connect()
        
    def set_hit_path(self,path):
        self.hit_path = path
    
    def test_hit_path(self,x,y):
        if self.parent: # Hmm, parent....
            self.parent.cairo.append_path(self.hit_path) # re-gen the path
            self.hit = self.parent.cairo.in_fill(x, y) # Test it. Sweet.
        return self.hit
        
    def set_allocation(self, x, y, w,h):
        self.allocation['x'],self.allocation['y'] = x,y
        self.allocation['w'],self.allocation['h'] = w,h
        
    def set_position(self, x, y):
        self.allocation['x'],self.allocation['y'] = x,y
        self.redraw()
    
    def redraw(self):
        self.emit("request-draw",None)
        
    def draw(self,cairo_context,event,clip=None):
        #cairo_context.new_path()
        cairo_context.transform(cairo.Matrix( 1,0,0,1,self.allocation['x'],self.allocation['y']))
        cairo_context.rectangle(0,0,self.allocation['w'],self.allocation['h'])
        cairo_context.clip()
        #cairo_context.move_to(self.allocation['x'],self.allocation['y'])
        pass
        
    def show(self):
        self.visible =True
        
    def hide(self):
        self.visible = False
        
    def get_allocation():
        pass
    
    def destroy():
        pass
    
    def set_size_request(width, height):
        self.size_request = width,height
        
    def get_size_request():
        return self.size_request
    
    #Signals
    #Hook to parent Window events
    def _parent_connect(self):
        
        if self.parent:
            self.parent.connect("button_press_event", self.button_press)
            self.parent.connect("key_press_event", self.key_press)
            self.parent.connect("button_release_event", self.button_release)
            self.parent.connect("key_release_event", self.key_release)
            self.parent.connect("motion-notify-event", self.motion_notify)
            self.parent.connect("configure_event", self.configure) #resize
            
            #self.parent.connect("click-event", self.clicked)
    
    def clicked(self,widget,event):
        self.emit("clicked",event)
        pass    
    def motion_notify(self,widget,event):
        self.emit("motion-notify-event",event)
        pass
    def button_press(self,widget,event):
        self.emit("button-press-event",event)
        pass
    def button_release(self,widget,event):
        self.emit("button-release-event",event)
        pass
    def key_press(self,widget,event):
        self.emit("key-press-event",event)
        pass
    def key_release(self,widget,event):
        self.emit("key-release-event",event)
        pass
    def configure(self,widget,event):
        #self.emit("configure-event",event)
        pass
    '''
    Returns mouse position relatively to widget
    '''
    def get_relative_pos(self,event): #Mouse click events
        return event.x - self.allocation['x'], event.y - self.allocation['y']

class SvgWidget(BaseWidget):

    def __init__(self):
        BaseWidget.__init__(self)
        self.svgs = None
        
    def draw(self, cr, event,clip=None):
        BaseWidget.draw(self,cr,event)
        
    def set_svgs(self, svgs):
        if type(svgs) is dict:
            self.svgs = {}
            for s in svgs.keys():
                self.svgs[s] = rsvg.Handle(file=svgs[s])
                print s , svgs[s]
                
        elif type(svgs) is list:
            self.svgs = []
            for s in svgs:
                self.svgs.append(rsvg.Handle(file=s))
                print s
        elif type(svgs) is str:
            self.svgs = rsvg.Handle(file=svgs)
        else:
            return
            
        self.init_skin()

    def init_skin(self):
        pass
        
    def button_press(self,w,e):
        BaseWidget.button_press(self,w,e)
        e.x,e.y = self.get_relative_pos(e)
        if self.test_hit_path(e.x,e.y):
            #ev = e #gtk.gdk.Event(gtk.gdk.BUTTON_PRESS)
            #e.x,e.y = x,y
            self.emit("clicked",e)
            print "Pressed"

    #def test_hit_path(self,x,y):
    #    BaseWidget.test_hit_path(self,x,y)

     #   return self.hit
        
class CairoHScale(SvgWidget):
    '''
    Uses Svgs for skinning. set_svgs() takes a dict : {'track': some0.svg, 'knob':some1.svg }
    '''
    __gsignals__ = {
        "value-changed": (gobject.SIGNAL_RUN_FIRST, None, (gtk.gdk.Event,)),
    }
    
    def __init__(self):
        SvgWidget.__init__(self)
        self._timer = -1
        self.drag = False
        #self.hit = False
        self.x,self.y = 0.0,0.0
        self.fx,self.fy = 0.0,0.0 # 'final'/relative values used to calculate scroll value
        self.fw,self.fh = 0.0,0.0

        self.range = (0.0,1.0)
        self.value_range = self.value = 1.0
    
    def init_skin(self):
        if not len(self.svgs):
            return
        self.fw,self.fh = self.svgs['knob'].props.width +2,self.svgs['knob'].props.height +2
        self.x,self.y = (self.svgs['track'].props.width-self.svgs['knob'].props.width) /2.0 ,(self.svgs['track'].props.height - self.svgs['knob'].props.height) /2.0
        #Add buffer area around knob
        self.x -= 1
        self.y -= 1
        print self.x, self.y
        
    def draw(self, cr, event,clip=None):
        SvgWidget.draw(self,cr,event)
        if not len(self.svgs):
            return
        
        self.svgs['track'].render_cairo(cr)
        
        cr.save()
        cr.set_source_rgba(1.0, 0.0, 0.0, 0.5)
        cr.rectangle(self.x,self.y,self.fw ,self.fh)
        cr.fill_preserve()
        self.set_hit_path( cr.copy_path_flat() )
        
        cr.restore()
        
        cr.new_path()
        if self.value > 0:
            cr.transform(cairo.Matrix(self.value,0,0,1,self.x+1,self.y+1))
            self.svgs['knob'].render_cairo(cr)
        
    def button_press(self,w,e):
        SvgWidget.button_press(self,w,e)
        
        self.drag = True
        if self.hit:
            self.fx,self.fy = e.x,e.y#self.get_relative_pos(e)
            self.calc_value()
            self.redraw()
            self.emit("value-changed",e)
            #self._req_redraw()
    
    def button_release(self,w,e):
        SvgWidget.button_release(self,w,e)
        self.drag = False
        #self._stop_redraw()

    def motion_notify(self,w,e):
        x,y = self.get_relative_pos(e)
        self.test_hit_path(x,y)
        
        #print self.x,self.fw, self.value
        
        if self.drag and self.hit:
            self.fx,self.fy = x,y
            self.calc_value()
            self.redraw()
            self.emit("value-changed",e)
            #print "Motion",self.value
            
    def _req_redraw(self):
        def _redraw():
            self.redraw()
            #print "Redraw"
            return True
        
        if self._timer == -1:
            self._timer = gobject.timeout_add(150,_redraw)
    
        
    def _stop_redraw(self):
        if self._timer != -1:
            gobject.source_remove(self._timer)
            self._timer = -1
    
    def set_range(self,a,b):
        self.range = (a,b)
    
    def get_range(self):
        return self.range
    
    def get_value(self):
        self.calc_value()
        return self.value_range
        
    def calc_value(self):
        print self.fx, self.fw
        self.value = float(self.fx )/ (self.fw)
        self.value_range = self.value * (self.range[1] - self.range[0]) + self.range[0]


    
        
class CairoSvgButton(SvgWidget):
    '''
    Svg button with states: normal, hilight, pressed . Use these accordingly with set_svgs()
    One svg split to 3 states
    '''
    def __init__(self):
        SvgWidget.__init__(self)
        self.state = 0
    
    def init_skin(self):
        pass
    
    def button_press(self,w,e):
        SvgWidget.button_press(self,w,e)
        self.state = 3
        
    def button_release(self,w,e):
        SvgWidget.button_release(self,w,e)
        self.state = 0
            
    def draw(self, cr, event,clip=None):
        SvgWidget.draw(self,cr,event)
        if not len(self.svgs):
            return
        
        w = self.svgs.props.width / 3.0
        h = self.svgs.props.height / 3.0
        
        cr.rectangle(0,0,w,h)
        self.set_hit_path( cr.copy_path_flat() )
        
        cr.new_path()
        cr.rectangle(0,0,w,h)
        cr.clip()
        cr.transform(cairo.Matrix(self.value,0,0,1,-self.state * w,0))
        self.svgs.render_cairo(cr)
        
            
gobject.type_register(BaseWidget)
gobject.type_register(SvgWidget)
gobject.type_register(CairoHScale)
